import React, { useLayoutEffect } from "react";
import { Router, useRoutes } from "react-router-dom";
import ReactDOM from "react-dom/client";
import { createClientRoutes } from "./routes";
import { AppContext, useAppData } from "./appContext";
import type { History } from "history";
import type { IRoutesById, IRouteComponents } from "./types";

let root: ReactDOM.Root | null = null;
let rootElement: HTMLElement;

export function __getRoot() {
  return root;
}

export function __getRootElement() {
  return rootElement;
}

export function Routes() {
  const { clientRoutes } = useAppData();
  return useRoutes(clientRoutes as any);
}

function BrowserRoutes(props: {
  routes: any;
  clientRoutes: any;
  pluginManager: any;
  history: History;
  basename: string;
  children: any;
}) {
  const { history } = props;
  const [state, setState] = React.useState({
    action: history.action,
    location: history.location,
  });
  useLayoutEffect(() => history.listen(setState), [history]);
  useLayoutEffect(() => {
    function onRouteChange(opts: any) {
      props.pluginManager.applyPlugins({
        key: "onRouteChange",
        type: "event",
        args: {
          routes: props.routes,
          clientRoutes: props.clientRoutes,
          location: opts.location,
          action: opts.action,
          basename: props.basename,
          isFirst: Boolean(opts.isFirst),
        },
      });
    }
    onRouteChange({
      location: state.location,
      action: state.action,
      isFirst: true,
    });
    return history.listen(onRouteChange);
  }, [history, props.routes, props.clientRoutes]);
  return (
    <Router
      navigator={history}
      location={state.location}
      basename={props.basename}
    >
      {props.children}
    </Router>
  );
}

const getBrowser = (
  opts: RenderClientOpts,
  routesElement: React.ReactElement
) => {
  const basename = opts.basename || "/";
  const clientRoutes = createClientRoutes({
    routesById: opts.routes,
    routeComponents: opts.routeComponents,
    loadingComponent: opts.loadingComponent,
    reactRouter5Compat:
      opts.reactRouter5Compat === undefined ? true : opts.reactRouter5Compat,
  });
  opts.pluginManager.applyPlugins({
    key: "patchClientRoutes",
    type: "event",
    args: {
      routes: clientRoutes,
    },
  });
  let rootContainer = (
    <BrowserRoutes
      basename={basename}
      routes={opts.routes}
      pluginManager={opts.pluginManager}
      clientRoutes={clientRoutes}
      history={opts.history}
    >
      {routesElement}
    </BrowserRoutes>
  );
  const Browser = () => {
    useLayoutEffect(() => {
      if (typeof opts.callback === "function") opts.callback();
    }, []);
    return (
      <AppContext.Provider
        value={{
          routes: opts.routes,
          routeComponents: opts.routeComponents,
          clientRoutes,
          basename,
          pluginManager: opts.pluginManager,
          history: opts.history,
          rootElement: opts.rootElement,
        }}
      >
        {rootContainer}
        {/* {dataflowProvider(rootContainer, opts)} */}
      </AppContext.Provider>
    );
  };
  return Browser;
};

export type RenderClientOpts = {
  basename?: string;
  loadingComponent?: React.ReactNode;
  reactRouter5Compat?: boolean;
  rootElement?: HTMLElement;
  hydrate?: boolean;
  components?: boolean;
  pluginManager: any;
  history: History;
  routes: IRoutesById;
  routeComponents: IRouteComponents;
  callback?: () => void;
};

export function renderClient(opts: RenderClientOpts) {
  rootElement = opts.rootElement || document.getElementById("root")!;
  const Browser = getBrowser(opts, <Routes />);
  if (opts.components) return Browser;
  if (opts.hydrate) {
    ReactDOM.hydrateRoot(rootElement, <Browser />);
    return;
  }
  root = ReactDOM.createRoot(rootElement);
  root.render(<Browser />);
  return;
}
